package org.dataframework.security.core;

import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.dataframework.security.core.db.Rule;
import org.dataframework.security.core.expression.BracketExpression;
import org.dataframework.security.core.expression.DateTimeValue;
import org.dataframework.security.core.expression.HiddenExpression;
import org.dataframework.security.core.expression.TruthExpression;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @Author: LiuCf
 * @Email: 1316640406@qq.com
 * @Date: 2021/7/3 18:38
 */
public class RuleAppliers {
    private RuleAppliers() {
    }

    public static void apply(Statement statement, List<Rule> rules) {
        if (statement instanceof Select) {
            select((Select) statement, rules);
        }
    }

    public static void select(Select select, List<Rule> rules) {
        selectBody(select.getSelectBody(), rules);
    }

    public static void selectBody(SelectBody selectBody, List<Rule> rules) {
        if (selectBody instanceof PlainSelect) {
            plainSelect((PlainSelect) selectBody, rules);
        } else if (selectBody instanceof WithItem) {
            withItem((WithItem) selectBody, rules);
        } else if (selectBody instanceof SetOperationList) {
            operationList((SetOperationList) selectBody, rules);
        }
    }

    public static void fromItem(FromItem fromItem, List<Rule> rules) {
        if (fromItem instanceof SubJoin) {
            subJoin((SubJoin) fromItem, rules);
        } else if (fromItem instanceof SubSelect) {
            subSelect((SubSelect) fromItem, rules);
        }
    }

    public static void plainSelect(PlainSelect plainSelect, List<Rule> rules) {
        Expression where = plainSelect.getWhere();
        if (where instanceof InExpression) {
            ItemsList rightItemsList = ((InExpression) where).getRightItemsList();
            if (rightItemsList instanceof SubSelect) {
                subSelect((SubSelect) rightItemsList, rules);
            }
        }

        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem instanceof Table) {
            Table fromTable = (Table) fromItem;
            String tableName = fromTable.getName();
            List<Expression> filter = new ArrayList<>();
            rules.stream().filter(r -> tableName.equalsIgnoreCase(r.getColumn().getTable().getTableName())).forEach(r -> {
                String columnName = r.getColumn().getColumnName();
                if (r.getOperator().equals(Operators.HIDDEN)) {
                    List<SelectItem> selectItems = plainSelect.getSelectItems();
                    Optional<SelectItem> itemOptional = selectItems.stream().filter(i -> i instanceof SelectExpressionItem && i.toString().contains(columnName)).findFirst();
                    if (itemOptional.isPresent()) {
                        ((SelectExpressionItem) itemOptional.get()).withExpression(new StringValue("null as"));
                    } else {
                        selectItems.add(new SelectExpressionItem().withExpression(new StringValue("null as " + columnName)));
                    }
                } else {
                    filter.add(expressionByRule(fromTable, r));
                }
            });
            if (!filter.isEmpty()) {
                Expression newWhere = null;
                if (where == null) {
                    newWhere = new TruthExpression();
                } else {
                    newWhere = new BracketExpression(where.toString());
                }
                for (int i = 0; i < filter.size(); i++) {
                    newWhere = new AndExpression().withLeftExpression(newWhere).withRightExpression(filter.get(i));
                }
                plainSelect.setWhere(newWhere);
            }
        } else {
            fromItem(fromItem, rules);
        }
        List<Join> joins = plainSelect.getJoins();
        if (joins != null && !joins.isEmpty()) {
            joins.forEach(j -> join(j, rules));
        }
    }

    public static void withItem(WithItem withItem, List<Rule> rules) {
        SelectBody selectBody = withItem.getSubSelect().getSelectBody();
        selectBody(selectBody, rules);
    }

    public static void join(Join join, List<Rule> rules) {
        if (join != null) {
            fromItem(join.getRightItem(), rules);
        }
    }

    public static void subJoin(SubJoin subJoin, List<Rule> rules) {
        if (subJoin == null) {
            return;
        }
        List<Join> joinList = subJoin.getJoinList();
        joinList.forEach(j -> join(j, rules));
    }

    public static void subSelect(SubSelect subSelect, List<Rule> rules) {
        selectBody(subSelect.getSelectBody(), rules);
    }

    public static void operationList(SetOperationList operationList, List<Rule> rules) {
        if (operationList != null) {
            operationList.getSelects().forEach(s -> selectBody(s, rules));
        }
    }

    public static Expression expressionByRule(Table table, Rule rule) {
        Column column = new Column(table, rule.getColumn().getColumnName());
        String dataType = rule.getColumn().getDataType().toLowerCase();
        Expression rightExpression = null;
        Expression startExpression = null;
        Expression endExpression = null;
        if (rule.getExpression() != null) {
            switch (dataType) {
                case "string":
                    rightExpression =  new StringValue(rule.getExpression().toString());
                    break;
                case "int":
                case "long":
                    rightExpression =  new LongValue(rule.getExpression().toString());
                    break;
                case "double":
                    rightExpression =   new DoubleValue(rule.getExpression().toString());
                    break;
                case "datetime":
                    rightExpression = new DateTimeValue(rule.getExpression().toString());
                    break;
                default:
                    throw new NotSupportException();
            }
        } else {
            Object param1 = rule.getParam1();
            Object param2 = rule.getParam2();
            switch (dataType) {
                case "string":
                    startExpression = new StringValue(param1.toString());
                    endExpression = new StringValue(param2.toString());
                    break;
                case "int":
                case "long":
                    startExpression = new LongValue(param1.toString());
                    endExpression = new LongValue(param2.toString());
                    break;
                case "double":
                    startExpression = new DoubleValue(param1.toString());
                    endExpression = new DoubleValue(param2.toString());
                    break;
                case "datetime":
                    startExpression = new DateTimeValue(param1.toString());
                    endExpression = new DateTimeValue(param2.toString());
                    break;
                default:
                    throw new NotSupportException();
            }
        }

        String operator = rule.getOperator().name();
        switch (operator) {
            case "HIDDEN":
                return new HiddenExpression();
            case "EQUALS":
                return new EqualsTo().withLeftExpression(column).withRightExpression(rightExpression);
            case "NOT_EQUALS":
                return new NotEqualsTo().withLeftExpression(column).withRightExpression(rightExpression);
            case "GREATER_THAN":
                return new GreaterThan().withLeftExpression(column).withRightExpression(rightExpression);
            case "MINOR_THAN":
                return new MinorThan().withLeftExpression(column).withRightExpression(rightExpression);
            case "GREATER_EQUALS":
                return new GreaterThanEquals().withLeftExpression(column).withRightExpression(rightExpression);
            case "MINOR_EQUALS":
                return new MinorThanEquals().withLeftExpression(column).withRightExpression(rightExpression);
            case "IS_NULL":
                return new IsNullExpression().withLeftExpression(column);
            case "IS_NOT_NULL":
                return new IsNullExpression().withNot(true).withLeftExpression(column);
            case "FULL_LIKE":
                return new LikeExpression().withLeftExpression(column).withRightExpression(rightExpression);
            case "NOT_LIKE":
                return new LikeExpression().withNot(true).withLeftExpression(column).withRightExpression(rightExpression);
            case "IN":
                return new InExpression().withLeftExpression(column).withRightExpression(rightExpression);
            case "NOT_IN":
                return new InExpression().withNot(true).withLeftExpression(column).withRightExpression(rightExpression);
            case "BETWEEN":
                return new Between().withLeftExpression(column).withBetweenExpressionStart(startExpression).withBetweenExpressionEnd(endExpression);
            case "CUSTOMIZE":
                return new BracketExpression(rightExpression);
            default:
                throw new NotSupportException();
        }
    }

}
